﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace gpsmid_wince {
	
	abstract class Projection {
		public const float PLANET_RAD_M = 6371000.8f;
		public const float PLANET_RAD = 6371.0008f;

		protected float centlat,centlon;
		protected int X0,Y0,WW,HH, WW2, HH2;
		/** pixels per radian at equator */
		protected float scale;
		//protected float EARTHRAD = 6400f;
		/** pixels per meter, ppr = ppm/erarthrad_m*pi */
		public void setScalePPM(float val) {
			setScalePPR(val*PLANET_RAD_M/(float)Math.PI);
		}
		public void setScalePPR(float val) {
			scale = val;
		}
		public float getScalePPR() {return scale;}
		public float getScalePPM() {return scale/PLANET_RAD_M*(float)Math.PI; }
		public void setCenter(float lat, float lon) {
			centlat = lat;
			centlon = lon;
		}
		public float getCenterLat() {return centlat;}
		public float getCenterLon() {return centlon;}
		public void setViewport(int W, int H) {
			setViewport(0,0,W,H);
		}
		public void setViewport(int x, int y, int w, int h) {
			X0 = x;
			Y0 = y;
			WW = w;
			HH = h;
			WW2 = WW/2;
			HH2 = HH/2;
		}
		public void getViewport(int[] pt) {
			pt[0] = X0; pt[1] = Y0; pt[2] = WW; pt[3] = HH;
		}

		abstract public Point toWin(float lat, float lon, float alt);
		public Point toWin(float lat, float lon) {return toWin(lat,lon,0); }

		abstract public void toReal(int x, int y, out float lat, out float lon);
		abstract public void screenInReal(float[] res);

		abstract public bool isOnScreen(float lat1, float lon1, float lat2, float lon2);
 	}


	class Projection2D: Projection {

		public override Point toWin(float lat, float lon, float alt) {
			Point pt = new Point(
				X0 + WW2 + (int)((lon-centlon)*scale*Math.Cos(lat) ), 
				Y0 + HH2 - (int)((lat-centlat)*scale) );
			return pt;
			
		}

		public override void toReal(int x, int y, out float lat, out float lon) {
			lat = (float)(-y + Y0 + HH2)/scale + centlat;
			lon = (float)((x - X0 - WW2)/scale/Math.Cos(lat) + centlon);
		}

		public override bool isOnScreen(float minLat, float minLon, float maxLat, float maxLon){
			float[] sc = new float[4];
			screenInReal(sc);
			if( sc[0] > maxLat || sc[2] < minLat) return false;
			if( sc[1] > maxLon || sc[3] < minLon) return false;
			return true;
		}

		//** returns an array [ minLat, minLon, maxLat, maxLon ]*/
		public override void screenInReal(float[] res) {
			float lt,ln;
			toReal(X0,Y0,out lt,out ln);
			res[2] = lt; 
			res[1] = ln;
			toReal(X0+WW,Y0+HH, out lt,out ln);
			res[0] = lt;
			res[3] = ln;
		}

	}


	class Projection3D: Projection {
		private double h,dx;

		public Projection3D(float dx, float h) {
			this.h=h;
			this.dx=dx;
		}
		const float KMPERDEGREE = 110;

		private struct Point3D {
			public double x,y,z;
			public Point3D(double xx, double yy, double zz){x=xx;y=yy;z=zz;}
			public void inc(double dx, double dy, double dz) {x+=dx;y+=dy;z+=dz;}
			public static Point3D operator+(Point3D a, Point3D b) {
				return new Point3D(){x=a.x+b.x, y=a.y+b.y, z=a.z+b.z };
			}
			public static Point3D operator-(Point3D a, Point3D b) {
				return new Point3D(){x=a.x-b.x, y=a.y-b.y, z=a.z-b.z };
			}
			public Point3D add(double dx, double dy,double dz){return new Point3D(){x=x+dx,y=y+dy,z=z+dz}; }
			public double len() {return Math.Sqrt(x*x+y*y+z*z); }
			public void unify() {
				double ln=len();
				x/=ln;y/=ln;z/=ln;
			}
			public static Point3D cross(Point3D x, Point3D y) {
				return new Point3D() {
					x= x.y*y.z - x.z*y.y,
					y= x.z*y.x - x.x*y.z,
					z= x.x*y.y - x.y*y.x
				};
			}
			/** muliplies by matrix with columns [a,b,c]^t*/
			public Point3D multiply(Point3D a, Point3D b, Point3D c) {
				return new Point3D() {
					/*x = a.x*x + b.x*y + c.x*z,
					y = a.y*x + b.y*y + c.y*z,
					z = a.z*x + b.z*y + c.z*z*/
					x= a*this, y=b*this, z=c*this
				};
			}
			public double dot(Point3D b) {
				return x*b.x + y*b.y + z*b.z;
			}
			public static double operator* (Point3D a, Point3D b) {return a.dot(b); }
			public override string ToString() {return "("+x+";"+y+";"+z+")";}
		}
		public double sqr(double a) {return a*a;}

		public override Point toWin(float lat, float lon, float alt) {			
			Point3D pdx = new Point3D(){x=dx, y=0, z=-h};
			//Point3D c = pdx;
			double beta=-sqr(pdx.len())/pdx.z;
			Point3D v2=pdx.add(0, 0, beta);
			v2.unify();
			Point3D pdx1 = pdx;
			pdx1.unify();
			Point3D v3 = Point3D.cross(pdx1, v2);

			Point3D l = new Point3D(
				(lat-centlat)*KMPERDEGREE,
				(lon-centlon)*Math.Cos(lat/180*Math.PI)*KMPERDEGREE,
				alt);
			l = l.multiply( pdx1,v2,v3 ) + pdx.multiply(pdx1,v2,v3);
			double f=3, p1=l.y*f/l.x, p2=l.z*f/l.x;
			Point pt = new Point((int)(-p2*WW2)+X0+WW2, -(int)(p1*HH2)+Y0+HH2);
			return pt;
		}

		public override void toReal(int x, int y, out float lat, out float lon) {
			lat = (float)(-y + Y0 + HH2)/scale + centlat;
			lon = (float)((x - X0 - WW2)/scale/Math.Cos(lat/180*Math.PI) + centlon);
		}

		public override void screenInReal(float[] res) {}

		public override bool isOnScreen(float lat1, float lon1, float lat2, float lon2){
			return true;
		}
	}
}
